package com.lcy.struct.bTreePlus.disk.recovery.undo;

import com.lcy.struct.bTreePlus.disk.constants.BPlusPublicConstants;
import com.lcy.struct.bTreePlus.disk.file.FileUtil;
import com.lcy.struct.bTreePlus.utils.BplusUtils;

import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author lucongyang
 * @date Created in  9:43
 * @description
 */
class WriteUndoLog {
    /**
     * undo log文件结构
     * 第一行：UNDO-START:transactionId COMMIT!
     * 如果DONE!和COMMIT!没有就空在那
     *
     * @param transactionId 事务id，全局唯一
     * @param undoStrList   某个操作的逆向语句
     * @see BPlusPublicConstants.UNDO_START_STR+transactionId(20位)+BPlusPublicConstants.UNDO_END_STR+BPlusPublicConstants.UNDO_END_COMMIT_STR
     * @see BPlusPublicConstants.UNDO_CONTENT + transactionId + BPlusPublicConstants.UNDO_CONTENT_CONTINUE + content
     * ...
     * @see BPlusPublicConstants.UNDO_CONTENT + transactionId + BPlusPublicConstants.UNDO_CONTENT_CONTINUE + content
     * 里面的参数详情请看
     * @see BPlusPublicConstants.UNDO_END_COMMIT_STR
     * @see BPlusPublicConstants.UNDO_END_STR
     * @see BPlusPublicConstants.UNDO_CONTENT_CONTINUE
     */
    void writeUndoStr(long transactionId, List<String> undoStrList) {
        // 获取undo的开始头部有没有写入,如果没有写入，则写入
        if (!UndoCenter.getInstance().transactionIdUndoStartPositionMap.containsKey(transactionId)) {
            String head = BPlusPublicConstants.UNDO_START_STR + transactionId;
            byte[] headBytes = new byte[BPlusPublicConstants.UNDO_START_STR.getBytes().length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH];
            BplusUtils.replaceArr(headBytes, head.getBytes(), 0);
            BplusUtils.initByteArray(headBytes, head.getBytes().length);
            long undoEndPosition = UndoCenter.getInstance().getUndoEndPosition();
            FileUtil.getInstance().straightRewrite(UndoCenter.getInstance().getUndoChannel(), undoEndPosition, BPlusPublicConstants.UNDO_PAGE_SIZE, true, headBytes);
            UndoCenter.getInstance().allocate(BPlusPublicConstants.UNDO_PAGE_SIZE);
            UndoCenter.getInstance().transactionIdUndoStartPositionMap.put(transactionId, undoEndPosition);
        }

        List<List<byte[]>> bytesList = new ArrayList<>();
        bytesList.add(new ArrayList<>());
        long totalLength = 0;
        for (String undoStr : undoStrList) {
            byte[] toWriteBytes = undoStr.getBytes();
            // 每页存数据的长度
            byte[] undoContentContinueBytes = BPlusPublicConstants.UNDO_CONTENT_CONTINUE.getBytes();
            byte[] undoContentBytes = BPlusPublicConstants.UNDO_CONTENT.getBytes();
            int size = BPlusPublicConstants.UNDO_PAGE_SIZE - BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH - undoContentBytes.length - undoContentContinueBytes.length;
            assert size > 0;
            int contentLength = ((int) Math.ceil(toWriteBytes.length / (size + 0.))) * BPlusPublicConstants.UNDO_PAGE_SIZE;

            byte[] desBytes = new byte[contentLength];
            BplusUtils.initByteArray(desBytes);
            for (int i = 0; i * size < toWriteBytes.length; i++) {
                // 1.写上BPlusPublicConstants.UNDO_CONTENT
                int from = i * BPlusPublicConstants.UNDO_PAGE_SIZE;
                BplusUtils.replaceArr(desBytes, undoContentBytes, from);
                // 2.写上transactionId
                from += undoContentBytes.length;
                BplusUtils.replaceArr(desBytes, (transactionId + "").getBytes(), from);

                from += BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH;
                if (i > 0) {
                    // 3.写上BPlusPublicConstants.UNDO_CONTENT_CONTINUE
                    BplusUtils.replaceArr(desBytes, undoContentContinueBytes, from);
                }
                // 4.写上content
                from += undoContentContinueBytes.length;
                byte[] bytes = Arrays.copyOfRange(toWriteBytes, i * size, (i + 1) * size);
                BplusUtils.replaceArr(desBytes, bytes, from);
            }

            // 加入总数组
            totalLength += contentLength;
            List<byte[]> list = bytesList.get(bytesList.size() - 1);
            long sum = list.stream().mapToLong(e -> e.length).sum();
            if (sum + contentLength > 10000000) {
                List<byte[]> newList = new ArrayList<>();
                newList.add(desBytes);
                bytesList.add(newList);
            } else {
                list.add(desBytes);
            }
        }

        UndoCenter.UNDO_END_POSITION_WRITE_LOCK.lock();
        try {
            long endPosition = UndoCenter.getInstance().getUndoEndPosition();
            for (List<byte[]> list : bytesList) {
                int sum = list.stream().mapToInt(e -> e.length).sum();
                byte[] desBytes = new byte[sum];
                int from = 0;
                for (int i = 0; i < list.size(); i++) {
                    BplusUtils.replaceArr(desBytes, list.get(i), from);
                    from += list.get(i).length;
                }
                FileChannel channel = UndoCenter.getInstance().getUndoChannel();
                FileUtil.getInstance().straightRewrite(channel, endPosition, sum, true, desBytes);
                endPosition += sum;
            }
            // 写末位置指针
            UndoCenter.getInstance().allocate(totalLength);
        } finally {
            UndoCenter.UNDO_END_POSITION_WRITE_LOCK.unlock();
        }
        // 写入缓存
        if (!UndoCenter.getInstance().transactionIdUndoStrListMap.containsKey(transactionId)) {
            UndoCenter.getInstance().transactionIdUndoStrListMap.put(transactionId, new ArrayList<>());
        }
        UndoCenter.getInstance().transactionIdUndoStrListMap.get(transactionId).addAll(undoStrList);
    }

    static WriteUndoLog getInstance() {
        return Singleton.INSTANCE;
    }

    private static class Singleton {
        private static final WriteUndoLog INSTANCE = new WriteUndoLog();
    }

    private WriteUndoLog() {
    }
}
